package cn.juque.lucenecandy.threads;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ClassUtil;
import cn.juque.common.base.PageInfo;
import cn.juque.common.utils.FastJsonUtil;
import cn.juque.lucenecandy.bo.IndexBO;
import cn.juque.lucenecandy.cache.IndexInfoCache;
import cn.juque.lucenecandy.core.base.BaseEntity;
import cn.juque.lucenecandy.core.base.BaseMessageEntity;
import cn.juque.lucenecandy.core.constants.StrConstant;
import cn.juque.lucenecandy.core.datasync.listener.MsgWriteListenerRender;
import cn.juque.lucenecandy.core.enums.DataSyncMsgTypeEnum;
import cn.juque.lucenecandy.core.enums.DocSyncTypeEnum;
import cn.juque.lucenecandy.helper.DocumentHelper;
import cn.juque.lucenecandy.helper.LuceneHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author juque
 * @version 1.0.0
 * <ul>
 *     <li>DataSyncMsgWriteThreadPool</li>
 * </ul>
 * @date 2024-01-02
 **/
@Slf4j
@Component("dataSyncMsgWriteThreadPool")
public class DataSyncMsgWriteThreadPool {

    private static final ThreadFactory THREAD_FACTORY = ThreadUtil.newNamedThreadFactory("index-msg-read-", true);

    private static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(1, THREAD_FACTORY);

    private static final BooleanQuery.Builder BOOL_BUILDER = new BooleanQuery.Builder();

    private static final PageInfo PAGE_INFO = new PageInfo();

    private static final Sort SORT = new Sort(new SortField("timestamp", SortField.Type.LONG));

    private static final AtomicBoolean IS_END = new AtomicBoolean(true);

    @Resource
    private DocumentHelper documentHelper;

    @Resource
    private LuceneHelper luceneHelper;

    @Resource
    private IndexInfoCache indexInfoCache;

    @Resource
    private MsgWriteListenerRender msgWriteListenerRender;

    public void init() {
        BOOL_BUILDER.add(IntPoint.newExactQuery(
                "msg_type", DataSyncMsgTypeEnum.WAIT_WRITE.getCode()), BooleanClause.Occur.MUST);
        PAGE_INFO.setPage(1);
        PAGE_INFO.setLimit(100);
        EXECUTOR.scheduleWithFixedDelay(this::execute, 0, 5, TimeUnit.SECONDS);
    }

    private void execute() {
        if (!IS_END.compareAndSet(true, false)) {
            return;
        }
        String className = ClassUtil.getClassName(BaseMessageEntity.class, false);
        IndexBO indexBO = this.indexInfoCache.get(className, false);
        String indexName = indexBO.getIndexName();
        try {
            List<Document> docList = this.luceneHelper.searchByPage(indexName, BOOL_BUILDER, SORT, PAGE_INFO, null);
            while (CollUtil.isNotEmpty(docList)) {
                List<BaseMessageEntity> entityList = this.documentHelper.toEntityList(docList, BaseMessageEntity.class);
                this.write(entityList);
                // 删除已同步的文档
                BooleanQuery.Builder builder = new BooleanQuery.Builder();
                entityList.forEach(f ->
                        builder.add(
                                new TermQuery(new Term(StrConstant.D_ID, f.getId())), BooleanClause.Occur.SHOULD));
                this.luceneHelper.deleteDocuments(indexName, builder.build());
                docList = this.luceneHelper.searchByPage(indexName, BOOL_BUILDER, SORT, PAGE_INFO, null);
            }
        } catch (Exception e) {
            log.error("文档同步异常", e);
        } finally {
            IS_END.set(true);
        }
    }

    private void write(List<BaseMessageEntity> list) {
        if (CollUtil.isEmpty(list)) {
            return;
        }
        list.forEach(f -> {
            if(!this.msgWriteListenerRender.before(f)) {
                return;
            }
            DocSyncTypeEnum syncType = DocSyncTypeEnum.valueOf(f.getSyncType());
            switch (syncType) {
                case ADD:
                    this.writeForAdd(f);
                    break;
                case UPDATE:
                    this.writeForUpdate(f);
                    break;
                case DEL:
                    this.writeForDel(f);
                    break;
                default:
                    break;
            }
            this.msgWriteListenerRender.after(f);
        });
    }

    private void writeForAdd(BaseMessageEntity entity) {
        String className = entity.getClassName();
        IndexBO indexBO = this.indexInfoCache.get(className, false);
        Class<? extends BaseEntity> tClass = ClassUtil.loadClass(className);
        List<? extends BaseEntity> list = this.documentHelper.toEntityList(entity.getBody(), tClass);
        if (CollUtil.isEmpty(list)) {
            return;
        }
        List<Document> docList = this.documentHelper.toDocumentList(list);
        this.luceneHelper.addDocuments(indexBO.getIndexName(), docList);
    }

    private void writeForUpdate(BaseMessageEntity entity) {
        String className = entity.getClassName();
        IndexBO indexBO = this.indexInfoCache.get(className, false);
        List<? extends BaseEntity> list = this.documentHelper.toEntityList(entity.getBody(), className);
        if (CollUtil.isEmpty(list)) {
            return;
        }
        // 删除历史文档
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        list.forEach(f -> builder.add(new TermQuery(new Term(StrConstant.D_ID, f.getId())), BooleanClause.Occur.SHOULD));
        this.luceneHelper.deleteDocuments(indexBO.getIndexName(), builder.build());
        // 新增文档
        List<Document> docList = this.documentHelper.toDocumentList(list);
        this.luceneHelper.addDocuments(indexBO.getIndexName(), docList);
    }

    private void writeForDel(BaseMessageEntity entity) {
        List<String> idList = FastJsonUtil.parseArray(entity.getBody(), String.class);
        if (CollUtil.isEmpty(idList)) {
            return;
        }
        String className = entity.getClassName();
        IndexBO indexBO = this.indexInfoCache.get(className, false);
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        idList.forEach(f -> builder.add(new TermQuery(new Term(StrConstant.D_ID, f)), BooleanClause.Occur.SHOULD));
        this.luceneHelper.deleteDocuments(indexBO.getIndexName(), builder.build());
    }
}
